Public Class SysNumbers

    Private Shared m_Primatives As Hashtable

    Private Enum Precedence
        None = 11
        Unary = 10      ' Not actually used.
        Power = 9
        Times = 8
        Div = 7
        IntDiv = 6
        Modulus = 5
        Plus = 4
    End Enum

    Public Shared Function EvaluateExpression(ByVal expression As String) As Double
        Dim expr As String
        Dim is_unary As Boolean
        Dim next_unary As Boolean
        Dim parens As Integer
        Dim expr_len As Integer
        Dim ch As String
        Dim lexpr As String
        Dim rexpr As String
        'Dim status As Long                 ' unused variable
        Dim best_pos As Integer
        Dim best_prec As Precedence

        ' Remove all spaces.
        expr = expression.Replace(" ", "")
        expr_len = Len(expr)
        If expr_len = 0 Then Return 0

        ' If we find + or - now, it is a unary operator.
        is_unary = True

        ' So far we have nothing.
        best_prec = Precedence.None

        ' Find the operator with the lowest precedence.
        ' Look for places where there are no open
        ' parentheses.
        For pos As Integer = 0 To expr_len - 1
            ' Examine the next character.
            ch = expr.Substring(pos, 1)

            ' Assume we will not find an operator. In
            ' that case, the next operator will not
            ' be unary.
            next_unary = False

            If ch = " " Then
                ' Just skip spaces. We keep them here
                ' to make the error messages easier to
            ElseIf ch = "(" Then
                ' Increase the open parentheses count.
                parens += 1

                ' A + or - after "(" is unary.
                next_unary = True
            ElseIf ch = ")" Then
                ' Decrease the open parentheses count.
                parens -= 1

                ' An operator after ")" is not unary.
                next_unary = False

                ' If parens < 0, too many ')'s.
                If parens < 0 Then
                    Throw New FormatException( _
                        "Too many close parentheses in '" & _
                        expression & "'")
                End If
            ElseIf parens = 0 Then
                ' See if this is an operator.
                If ch = "^" Or ch = "*" Or _
                   ch = "/" Or ch = "\" Or _
                   ch = "%" Or ch = "+" Or _
                   ch = "-" _
                Then
                    ' An operator after an operator
                    ' is unary.
                    next_unary = True

                    ' See if this operator has higher
                    ' precedence than the current one.
                    Select Case ch
                        Case "^"
                            If best_prec >= Precedence.Power _
                                Then
                                best_prec = Precedence.Power
                                best_pos = pos
                            End If

                        Case "*", "/"
                            If best_prec >= Precedence.Times _
                                Then
                                best_prec = Precedence.Times
                                best_pos = pos
                            End If

                        Case "\"
                            If best_prec >= Precedence.IntDiv _
                                Then
                                best_prec = Precedence.IntDiv
                                best_pos = pos
                            End If

                        Case "%"
                            If best_prec >= Precedence.Modulus _
                                Then
                                best_prec = Precedence.Modulus
                                best_pos = pos
                            End If

                        Case "+", "-"
                            ' Ignore unary operators
                            ' for now.
                            If (Not is_unary) And _
                                best_prec >= Precedence.Plus _
                            Then
                                best_prec = Precedence.Plus
                                best_pos = pos
                            End If
                    End Select
                End If
            End If
            is_unary = next_unary
        Next pos

        ' If the parentheses count is not zero,
        ' there's a ')' missing.
        If parens <> 0 Then
            Throw New FormatException( _
                "Missing close parenthesis in '" & _
                expression & "'")
        End If

        ' Hopefully we have the operator.
        If best_prec < Precedence.None Then
            lexpr = expr.Substring(0, best_pos)
            rexpr = expr.Substring(best_pos + 1)
            Select Case expr.Substring(best_pos, 1)
                Case "^"
                    Return _
                        EvaluateExpression(lexpr) ^ _
                        EvaluateExpression(rexpr)
                Case "*"
                    Return _
                        EvaluateExpression(lexpr) * _
                        EvaluateExpression(rexpr)
                Case "/"
                    Return _
                        EvaluateExpression(lexpr) / _
                        EvaluateExpression(rexpr)
                Case "\"
                    Return _
                        CLng(EvaluateExpression(lexpr)) \ _
                        CLng(EvaluateExpression(rexpr))
                Case "%"
                    Return _
                        EvaluateExpression(lexpr) Mod _
                        EvaluateExpression(rexpr)
                Case "+"
                    Return _
                        EvaluateExpression(lexpr) + _
                        EvaluateExpression(rexpr)
                Case "-"
                    Return _
                        EvaluateExpression(lexpr) - _
                        EvaluateExpression(rexpr)
            End Select
        End If

        ' If we do not yet have an operator, there
        ' are several possibilities:
        '
        ' 1. expr is (expr2) for some expr2.
        ' 2. expr is -expr2 or +expr2 for some expr2.
        ' 3. expr is Fun(expr2) for a function Fun.
        ' 4. expr is a primitive.
        ' 5. It's a literal like "3.14159".

        ' Look for (expr2).
        If expr.StartsWith("(") And expr.EndsWith(")") Then
            ' Remove the parentheses.
            Return EvaluateExpression(expr.Substring(1, _
                expr_len - 2))
            Exit Function
        End If

        ' Look for -expr2.
        If expr.StartsWith("-") Then
            Return -EvaluateExpression(expr.Substring(1))
        End If

        ' Look for +expr2.
        If expr.StartsWith("+") Then
            Return EvaluateExpression(expr.Substring(2))
        End If

        ' Look for Fun(expr2).
        If expr_len > 5 And expr.EndsWith(")") Then
            ' Find the first (.
            Dim paren_pos As Integer = expr.IndexOf("(")
            If paren_pos > 0 Then
                ' See what the function is.
                lexpr = expr.Substring(0, paren_pos)
                rexpr = expr.Substring(paren_pos + 1, expr_len _
                    - paren_pos - 2)
                Select Case lexpr.ToLower
                    Case "sin"
                        Return Math.Sin(EvaluateExpression(rexpr))
                    Case "cos"
                        Return Math.Cos(EvaluateExpression(rexpr))
                    Case "tan"
                        Return Math.Tan(EvaluateExpression(rexpr))
                    Case "sqrt"
                        Return Math.Sqrt(EvaluateExpression(rexpr))
                    Case "factorial"
                        Return Factorial(EvaluateExpression(rexpr))
                        ' Add other functions (including
                        ' program-defined functions) here.
                End Select
            End If
        End If

        m_Primatives = New Hashtable

        ' See if it's a primitive.
        If m_Primatives.Contains(expr) Then
            ' Return the corresponding value,
            ' converted into a Double.
            Try
                ' Try to convert the expression into a value.
                Dim value As Double = _
                    Double.Parse(m_Primatives.Item(expr).ToString)
                Return value
            Catch ex As Exception
                Throw New FormatException( _
                    "Primative '" & expr & _
                    "' has value '" & _
                    m_Primatives.Item(expr).ToString & _
                    "' which is not a Double.")
            End Try
        End If

        'Remove the $ and , 's
        expr = expr.Replace("$", "")
        expr = expr.Replace(",", "")

        ' It must be a literal like "2.71828".
        Try
            ' Try to convert the expression into a Double.
            Dim value As Double = Double.Parse(expr)
            Return value
        Catch ex As Exception
            Throw New FormatException( _
                "Error evaluating '" & expression & _
                "' as a constant.")
        End Try
    End Function

    ' Return the factorial of the expression.
    Private Shared Function Factorial(ByVal value As Double) As Double
        Dim result As Double

        ' Make sure the value is an integer.
        If CLng(value) <> value Then
            Throw New ArgumentException( _
                "Parameter to Factorial function must be an " & _
                    "integer in Factorial(" & _
                Format$(value) & ")")
        End If

        result = 1
        Do While value > 1
            result = result * value
            value = value - 1
        Loop
        Return result
    End Function

    Public Shared Function AreEqual(ByVal v1 As Decimal, ByVal v2 As Decimal, ByVal numDecimals As Integer) As Boolean
        'Checks to see if the two values are equal down to the penny.  If they are, returns true.
        If v1 = Decimal.MinValue And v2 = Decimal.MinValue Then
            Return True
        ElseIf v1 = Decimal.MinValue Then
            Return False
        ElseIf v2 = Decimal.MinValue Then
            Return False
        End If
        Dim d1 As Int64 = v1 * 10 ^ numDecimals
        Dim d2 As Int64 = v2 * 10 ^ numDecimals
        If d1 = d2 Then
            Return True
        Else
            Return False
        End If
        'this doesn't work  well.
        'If Decimal.Round(v1, numDecimals) = Decimal.Round(v2, numDecimals) Then
        '    Return True
        'Else
        '    Return False
        'End If
    End Function

    Public Shared Function FormatTimeSpan(ByVal ts As System.TimeSpan) As String
        Dim sRet As String
        Dim sHours As String
        Dim sMin As String
        Dim sSeconds As String
        Try
            sHours = Format(ts.Hours, "00")
            sMin = Format(ts.Minutes, "00")
            sSeconds = Format(ts.Seconds, "00")
            sRet = sHours + ":" + sMin + ":" + sSeconds
            Return sRet
        Catch ex As Exception
            Return ""
        End Try
    End Function

    Public Shared Function GetPreviousMonthEnd() As DateTime
        Dim d As DateTime = DateTime.Now
        Dim dRet As DateTime = New DateTime(d.Year, d.Month, 1)
        dRet = dRet.AddDays(-1)
        Return dRet
    End Function
    Public Shared Function GetPreviousMonthEnd(ByVal dt As DateTime) As DateTime
        Dim dRet As DateTime = New DateTime(dt.Year, dt.Month, 1)
        dRet = dRet.AddDays(-1)
        Return dRet
    End Function
    Public Shared Function GetNextMonthEnd() As DateTime
        Dim d As DateTime = DateTime.Now
        Dim dRet As DateTime = New DateTime(d.Year, d.Month, 1)
        dRet = dRet.AddMonths(2)
        dRet = dRet.AddDays(-1)
        Return dRet
    End Function
    Public Shared Function GetNextMonthEnd(ByVal dt As DateTime) As DateTime
        Dim dRet As DateTime = New DateTime(dt.Year, dt.Month, 1)
        dRet = dRet.AddMonths(2)
        dRet = dRet.AddDays(-1)
        Return dRet
    End Function
    Public Shared Function GetCurrentMonthEnd() As DateTime
        Dim d As DateTime = DateTime.Now
        Dim dRet As DateTime = New DateTime(d.Year, d.Month, 1)
        dRet = dRet.AddMonths(1)
        dRet = dRet.AddDays(-1)
        Return dRet
    End Function

    Public Shared Function GetCurrentMonthEnd(ByVal d As DateTime) As DateTime
        Dim dMonth As Integer
        dMonth = d.Month + 1
        If dMonth = 13 Then
            d = d.AddYears(1)
            dMonth = 1
        End If
        Dim dRet As DateTime = New DateTime(d.Year, dMonth, 1)
        dRet = dRet.AddDays(-1)
        Return dRet
    End Function

    Public Shared Function GetIntegerFromString(ByVal s As String) As Integer
        'Gets the date from a string, if text is blank, returns the
        'minvalue for the datetime.
        Dim iRet As Integer = 0
        iRet = Integer.MinValue
        Try
            If Trim(s).Length = 0 Then
                Return iRet
            End If
            iRet = CType(s, Integer)
            Return iRet
        Catch ex As Exception
            Return iRet
            Throw ex
        End Try
    End Function

    Public Shared Function GetDateFromString(ByVal s As String) As DateTime
        'Gets the date from a string, if text is blank, returns the
        'minvalue for the datetime.
        Dim dtRet As DateTime = New DateTime
        dtRet = DateTime.MinValue
        Try
            If Trim(s).Length = 0 Then
                Return dtRet
            End If
            dtRet = CType(s, DateTime)
            Return dtRet
        Catch ex As Exception
            Return dtRet
            Throw ex
        End Try
    End Function
    Public Shared Function GetDecimalFromString(ByVal s As String) As Decimal
        'Gets the date from a string, if text is blank, returns the
        'minvalue for the decimal.
        Dim dRet As Decimal
        Dim bDiv100 As Boolean = False
        dRet = Decimal.MinValue
        '% Format needs to be handled:
        If Right(s, 1) = "%" Then
            'Strip off the percent and divide by 100:
            s = Left(s, s.Length - 1)
            bDiv100 = True
        End If
        Try
            If Trim(s).Length = 0 Then
                Return dRet
            End If
            dRet = CType(s, Decimal)
            If bDiv100 Then
                dRet = dRet / 100
            End If
            Return dRet
        Catch ex As Exception
            ' Throw ex
            Return 0
        End Try
    End Function
    Public Shared Function isDate(ByVal s As String) As Boolean
        Try
            Dim d As DateTime = New DateTime
            d = CDate(s)
            Return True
        Catch ex As Exception
            Return False
        End Try
    End Function


    Public Shared Function isNumber(ByVal s As String) As Boolean
        If IsNumeric(s) Then
            Return True
        Else
            Return False
        End If
    End Function

    Public Shared Function isNumericEx(ByVal s As String) As Boolean
        '2005-3-31 ported to .Net by MBravo.
        'Original version is in xFuncs.Pas by RGrossman, 2005-1-29
        'Now allows for % sign at end of string, E, and a second minus after the E.
        Dim ktr As Integer
        Dim bDecimal As Boolean = False
        Dim bExponent As Boolean = False
        Dim bRet As Boolean = False
        Dim bOk As Boolean = False

        'If it's null, it's not a number.
        If (s = Nothing) Or (s.Length = 0) Then
            Return False
        End If
        For ktr = 1 To s.Length
            bOk = False
            'is a number
            If InStr("0123456789", s.Chars(ktr - 1)) > 0 Then
                bOk = True
            End If
            'First Exp
            If (s.Chars(ktr - 1) = "E") And (Not bExponent) And (ktr > 1) Then
                bExponent = True
                bOk = True
            End If
            'Plus or minus in first position
            If (ktr = 1) And (InStr("+-", s.Chars(ktr - 1)) > 0) Then
                bOk = True
            End If
            'Plus or Minus okay just after "E"
            If (InStr("+-", s.Chars(ktr - 1)) > 0) And (ktr > 1) Then
                If (Char.ToUpper(s.Chars(ktr - 2)) = "E") Then
                    bOk = True
                End If
            End If

            'Percent okay in last position only:
            If (ktr = s.Length) And (s.Chars(ktr - 1) = "%") Then
                bOk = True
            End If
            'Decimal okay once:
            If (s.Chars(ktr - 1) = ".") And (Not bDecimal) Then
                bDecimal = True
                bOk = True
            End If
            'Otherwise, this is not a numeric string.
            If Not bOk Then
                Return False
            End If
        Next
        Return True
    End Function


End Class
